package cn.sc.summer.swagger.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.sc.summer.constant.enums.SwaggerDisplayEnum;
import cn.sc.summer.constant.project.ProjectConstant;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.constant.util.AssertUtil;
import cn.sc.summer.exception.base.ExceptionResult;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.http.HttpMethod;
import org.springframework.lang.NonNull;
import org.springframework.web.context.request.async.DeferredResult;
import springfox.documentation.builders.*;
import springfox.documentation.schema.Annotations;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 类名：swagger2配置文件
 *
 * @author a-xin
 * @date 2024/2/7 09:01
 */
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
@Profile({"dev-8", "dev-17"})
public class Swagger2Config implements ModelPropertyBuilderPlugin {

    private static final String VERSION = "3.0.3";

    @Override
    public void apply(ModelPropertyContext context) {
        //为枚举字段设置注释
        descForEnumFields(context);
    }

    @Override
    public boolean supports(@NonNull DocumentationType documentationType) {
        return true;
    }

    @Bean
    public Docket createDocket() {
        log.info("==> Start initializing the swagger information...");
        return new Docket(DocumentationType.SWAGGER_2)
                .genericModelSubstitutes(DeferredResult.class)
                .forCodeGeneration(true)
                .pathMapping("/")
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .paths(PathSelectors.any())
                .build()
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts())
                .globalRequestParameters(getGlobalRequestParameters())
                .globalResponses(HttpMethod.GET, getGlobalResponseMessage())
                .globalResponses(HttpMethod.POST, getGlobalResponseMessage())
                .apiInfo(new ApiInfoBuilder()
                        .title(ProjectConstant.SERVER_NAME + " : api document")
                        .version(Swagger2Config.VERSION)
                        .contact(new Contact("a-xin", "www.summer.sc.cn", "1846221495@qq.com"))
                        .license("summer LTD.Co")
                        .build())
                .enable(true);
    }

    /**
     * 设置接口默认添加的参数信息
     *
     * @return 参数信息
     */
    private List<RequestParameter> getGlobalRequestParameters() {
        List<RequestParameter> parameters = new ArrayList<>();
        parameters.add(new RequestParameterBuilder()
                .name("id")
                .description("id")
                .required(false)
                .in(ParameterType.QUERY)
                .query(q -> q.model(m -> m.scalarModel((ScalarType.STRING))))
                .required(false)
                .build());
        return parameters;
    }

    /**
     * 获取所有的异常返回信息
     *
     * @return 异常信息集合
     */
    private List<Response> getGlobalResponseMessage() {
        List<ExceptionInfo> exceptionInfos = getExceptionInfo();
        return AssertUtil.functionIfPre(exceptionInfos, CollUtil::isNotEmpty, () -> {
            List<Response> responseList = new ArrayList<>();
            exceptionInfos.stream()
                    .forEach(exceptionInfo ->
                            responseList.add(new ResponseBuilder().code(exceptionInfo.getCode()).description(exceptionInfo.getInfo()).build()));
            return responseList;
        }, new ArrayList<>());
    }

    /**
     * 获取所有异常信息
     *
     * @return 异常信息返回数据
     */
    private List<ExceptionInfo> getExceptionInfo() {

        List<ExceptionInfo> exceptionInfoList = new ArrayList<>();
        // 扫描所有类，找到实现了接口的类
        Set<Class<?>> implementingClasses = findImplementingClasses();
        // 检查每个实现类是否是枚举类
        for (Class<?> implementingClass : implementingClasses) {
            if (implementingClass.isEnum()) {
                // 获取枚举常量数组
                Object[] enumConstants = implementingClass.getEnumConstants();
                String enumName = implementingClass.getSimpleName();
                ExceptionInfo exceptionInfo = new ExceptionInfo();
                // 遍历枚举常量
                for (Object constant : enumConstants) {
                    try {
                        // 获取枚举常量的方法
                        Method messageMethod = constant.getClass().getMethod("getMessage");
                        Method codeMethod = constant.getClass().getMethod("getCode");
                        // 调用方法获取字段值
                        String message = (String) messageMethod.invoke(constant);
                        int code = (int) codeMethod.invoke(constant);
                        exceptionInfo.setCode(String.valueOf(code));
                        exceptionInfo.setInfo(enumName + ":" + constant + ":" + message);
                        exceptionInfoList.add(exceptionInfo);
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }
            }
        }
        return exceptionInfoList;
    }

    @Data
    static class ExceptionInfo implements Serializable {

        private static final long serialVersionUID = 1L;

        /**
         * 错误码
         */
        private String code;

        /**
         * 错误信息
         */
        private String info;

    }

    /**
     * 为枚举字段设置注释
     *
     * @param context 上下文
     */
    private void descForEnumFields(ModelPropertyContext context) {
        Optional<ApiModelProperty> annotation = Optional.empty();

        // 找到 @ApiModelProperty 注解修饰的枚举类
        if (context.getBeanPropertyDefinition().isPresent()) {
            annotation = Annotations.findPropertyAnnotation(context.getBeanPropertyDefinition().get(), ApiModelProperty.class);
        }

        //没有@ApiModelProperty 或者 notes 属性没有值，直接返回
        if (!annotation.isPresent() || StrUtil.isBlank((annotation.get()).notes())) {
            return;
        }

        //@ApiModelProperties中的notes指定的class类型
        Class<?> rawPrimaryType;
        try {
            rawPrimaryType = Class.forName((annotation.get()).notes());
        } catch (ClassNotFoundException e) {
            //如果指定的类型无法转化，直接忽略
            return;
        }

        Object[] subItemRecords;
        // 判断 rawPrimaryType 是否为枚举，且实现了 SwaggerDisplayEnum 接口
        if (Enum.class.isAssignableFrom(rawPrimaryType) && SwaggerDisplayEnum.class.isAssignableFrom(rawPrimaryType)) {
            // 拿到枚举的所有的值
            subItemRecords = rawPrimaryType.getEnumConstants();
        } else {
            return;
        }

        final List<String> displayValues = Arrays.stream(subItemRecords).filter(Objects::nonNull)
                // 调用枚举类的 description 方法
                .map(p -> ((SwaggerDisplayEnum) p).description()).filter(Objects::nonNull).collect(Collectors.toList());

        String joinText = " (" + String.join("; ", displayValues) + ")";
        try {
            // 拿到字段上原先的描述
            Field mField = PropertySpecificationBuilder.class.getDeclaredField("description");
            mField.setAccessible(true);
            // context 中的 builder 对象保存了字段的信息
            joinText = mField.get(context.getSpecificationBuilder()) + joinText;
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        // 设置新的字段说明并且设置字段类型
        context.getSpecificationBuilder().description(joinText);
    }

    /**
     * 设置请求头信息
     *
     * @return 请求头
     */
    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> apiKeyList = new ArrayList<>();
        //配置header头1
        ApiKey tokenAccess = new ApiKey(TokenConstant.ACCESS_TOKEN, TokenConstant.ACCESS_TOKEN, "header");
        apiKeyList.add(tokenAccess);
        return apiKeyList;
    }

    /**
     * 设置接口授权信息
     *
     * @return 授权信息
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContextList = new ArrayList<>();
        List<SecurityReference> securityReferenceList = new ArrayList<>();
        //为每个api添加请求头
        securityReferenceList.add(new SecurityReference(TokenConstant.ACCESS_TOKEN, scopes()));
        securityContextList.add(SecurityContext
                .builder()
                .securityReferences(securityReferenceList)
                .forPaths(PathSelectors.any())
                .build()
        );
        return securityContextList;
    }

    /**
     * 设置请求头作用域
     *
     * @return 作用域
     */
    private AuthorizationScope[] scopes() {
        //作用域为全局
        return new AuthorizationScope[]{new AuthorizationScope("global", "accessAnything")};
    }

    /**
     * 获取指定接口所有实现类
     *
     * @return 实现类集合
     */
    private Set<Class<?>> findImplementingClasses() {
        // 使用Spring的类扫描机制
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AssignableTypeFilter(ExceptionResult.class));
        Set<Class<?>> implementingClasses = new HashSet<>();
        Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents("cn.sc.summer.exception.**");
        for (BeanDefinition beanDefinition : beanDefinitions) {
            try {
                implementingClasses.add(Class.forName(beanDefinition.getBeanClassName()));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return implementingClasses;
    }

}

